Query.java
package org.codefilarete.stalactite.query.model;
import java.util.Map;
import java.util.function.BiFunction;
import org.codefilarete.reflection.MethodReferenceDispatcher;
import org.codefilarete.stalactite.query.builder.QuerySQLBuilderFactory.QuerySQLBuilder;
import org.codefilarete.stalactite.query.model.OrderByChain.Order;
import org.codefilarete.stalactite.query.model.Query.FluentLimitClause;
import org.codefilarete.stalactite.query.model.SelectChain.Aliasable;
import org.codefilarete.stalactite.query.model.SelectChain.AliasableExpression;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.tool.collection.KeepOrderSet;
import org.codefilarete.tool.function.SerializableTriFunction;
import org.codefilarete.tool.reflect.MethodDispatcher;
/**
* A support for a SQL query, trying to be closest as possible to a real select query syntax and implementing the most simple/common usage.
* No syntax validation is done. Final printing can be made by {@link QuerySQLBuilder}
*
* @author Guillaume Mary
* @see QuerySQLBuilder
* @see QueryEase
*/
public class Query implements FromAware, WhereAware, HavingAware, OrderByAware, LimitAware<FluentLimitClause>, QueryProvider<Query>,
QueryStatement, UnionAware {
private final FluentSelectClause select;
private final Select selectDelegate;
private final FluentFromClause from;
private final From fromDelegate;
private final FluentWhereClause where;
private final Where whereDelegate;
private final FluentGroupByClause groupBy;
private final GroupBy groupByDelegate;
private final FluentHavingClause having;
private final Having havingDelegate;
private final FluentOrderByClause orderBy;
private final OrderBy orderByDelegate;
private final FluentLimitClause limit;
private final Limit limitDelegate;
public Query() {
this(null);
}
public Query(Fromable rootTable) {
this(new Select(), new From(rootTable), new Where(), new GroupBy(), new Having(), new OrderBy(), new Limit());
}
public Query(Select selectDelegate,
From fromDelegate,
Where whereDelegate,
GroupBy groupByDelegate,
Having havingDelegate,
OrderBy orderByDelegate,
Limit limitDelegate) {
this.selectDelegate = selectDelegate;
this.select = new MethodReferenceDispatcher()
.redirect(SelectChain.class, this.selectDelegate, true)
.redirect((SerializableTriFunction<FluentSelectClause, String, Class, FluentSelectClauseAliasableExpression>)
FluentSelectClause::add, new BiFunction<String, Class, FluentSelectClauseAliasableExpression>() {
@Override
public FluentSelectClauseAliasableExpression apply(String s, Class aClass) {
AliasableExpression<Select> add = Query.this.selectDelegate.add(s, aClass);
return new MethodDispatcher()
.redirect(Aliasable.class, alias -> {
add.as(alias);
return null; // we don't care about the returned object since the proxy is returned
}, true)
.fallbackOn(select)
.build(FluentSelectClauseAliasableExpression.class);
}
})
.redirect(FromAware.class, this)
.redirect(QueryProvider.class, this)
.build(FluentSelectClause.class);
this.fromDelegate = fromDelegate;
this.from = new MethodDispatcher()
.redirect(JoinChain.class, this.fromDelegate, true)
.redirect(WhereAware.class, this)
.redirect(GroupByAware.class, this)
.redirect(OrderByAware.class, this)
.redirect(LimitAware.class, this)
.redirect(QueryProvider.class, this)
.build(FluentFromClause.class);
this.whereDelegate = whereDelegate;
this.where = new MethodDispatcher()
.redirect(CriteriaChain.class, this.whereDelegate, true)
.redirect(Iterable.class, whereDelegate)
.redirect(GroupByAware.class, this)
.redirect(OrderByAware.class, this)
.redirect(LimitAware.class, this)
.redirect(QueryProvider.class, this)
.redirect(UnionAware.class, this)
.build(FluentWhereClause.class);
this.groupByDelegate = groupByDelegate;
this.groupBy = new MethodDispatcher()
.redirect(GroupByChain.class, this.groupByDelegate, true)
.redirect(HavingAware.class, this)
.redirect(OrderByAware.class, this)
.redirect(LimitAware.class, this)
.redirect(QueryProvider.class, this)
.redirect(UnionAware.class, this)
.build(FluentGroupByClause.class);
this.havingDelegate = havingDelegate;
this.having = new MethodDispatcher()
.redirect(CriteriaChain.class, this.havingDelegate, true)
.redirect(OrderByAware.class, this)
.redirect(LimitAware.class, this)
.redirect(QueryProvider.class, this)
.redirect(UnionAware.class, this)
.build(FluentHavingClause.class);
this.orderByDelegate = orderByDelegate;
this.orderBy = new MethodDispatcher()
.redirect(OrderByChain.class, this.orderByDelegate, true)
.redirect(LimitAware.class, this)
.redirect(QueryProvider.class, this)
.redirect(UnionAware.class, this)
.build(FluentOrderByClause.class);
this.limitDelegate = limitDelegate;
this.limit = new MethodDispatcher()
.redirect(LimitChain.class, this.limitDelegate, true)
.redirect(QueryProvider.class, this)
.redirect(UnionAware.class, this)
.build(FluentLimitClause.class);
}
public FluentSelectClause getSelect() {
return this.select;
}
/**
* @return a concrete implementation of a select
*/
public Select getSelectDelegate() {
return selectDelegate;
}
public JoinChain getFrom() {
return this.from;
}
public From getFromDelegate() {
return fromDelegate;
}
public FluentWhereClause getWhere() {
return where;
}
public Where getWhereDelegate() {
return whereDelegate;
}
public GroupBy getGroupByDelegate() {
return groupByDelegate;
}
public Having getHavingDelegate() {
return havingDelegate;
}
public OrderBy getOrderByDelegate() {
return orderByDelegate;
}
public Limit getLimitDelegate() {
return limitDelegate;
}
@Override
public KeepOrderSet<Selectable<?>> getColumns() {
return this.selectDelegate.getColumns();
}
@Override
public Map<Selectable<?>, String> getAliases() {
return this.selectDelegate.getAliases();
}
public FluentSelectClause select(Iterable<? extends Selectable<?>> selectables) {
selectables.forEach(this.selectDelegate::add);
return this.select;
}
public FluentSelectClause select(Selectable<?> expression, Selectable<?>... expressions) {
this.selectDelegate.add(expression, expressions);
return select;
}
public FluentSelectClauseAliasableExpression select(String expression, Class<?> javaType) {
AliasableExpression<Select> add = this.selectDelegate.add(expression, javaType);
return new MethodDispatcher()
.redirect(Aliasable.class, alias -> {
add.as(alias);
return null; // we don't care about returned object since proxy is returned
}, true)
.redirect(FluentSelectClause.class, select)
.build(FluentSelectClauseAliasableExpression.class);
}
public FluentSelectClause select(Selectable<?> column, String alias) {
this.selectDelegate.add(column, alias);
return select;
}
public FluentSelectClause select(Selectable<?> col1, String alias1, Selectable<?> col2, String alias2) {
this.selectDelegate.add(col1, alias1, col2, alias2);
return select;
}
public FluentSelectClause select(Selectable<?> col1, String alias1, Selectable<?> col2, String alias2, Selectable<?> col3, String alias3) {
this.selectDelegate.add(col1, alias1, col2, alias2, col3, alias3);
return select;
}
public FluentSelectClause select(Map<? extends Selectable<?>, String> aliasedColumns) {
this.selectDelegate.add(aliasedColumns);
return select;
}
@Override
public FluentFromClause from(Fromable leftTable, Fromable rightTable, String joinCondition) {
this.fromDelegate.setRoot(leftTable).innerJoin(rightTable, joinCondition);
return from;
}
@Override
public FluentFromClause from(Fromable leftTable) {
this.fromDelegate.setRoot(leftTable);
return from;
}
@Override
public FluentFromClause from(QueryProvider<?> query, String alias) {
this.fromDelegate.setRoot(query.getQuery().asPseudoTable(), alias);
return from;
}
@Override
public FluentFromClause from(Fromable leftTable, String tableAlias) {
this.fromDelegate.setRoot(leftTable, tableAlias);
return from;
}
@Override
public FluentFromClause from(Fromable leftTable, String leftTableAlias, Fromable rightTable, String rightTableAlias, String joinCondition) {
this.fromDelegate.setRoot(leftTable).innerJoin(rightTable, rightTableAlias, joinCondition);
return from;
}
@Override
public <I> FluentFromClause from(JoinLink<?, I> leftColumn, JoinLink<?, I> rightColumn) {
this.fromDelegate.setRoot(leftColumn.getOwner()).innerJoin(leftColumn, rightColumn);
return from;
}
@Override
public <O> FluentWhereClause where(Column<?, O> column, CharSequence condition) {
this.whereDelegate.and(new ColumnCriterion(column, condition));
return where;
}
@Override
public <O> FluentWhereClause where(Column<?, O> column, ConditionalOperator<? super O, ?> condition) {
this.whereDelegate.and(new ColumnCriterion(column, condition));
return where;
}
@Override
public FluentWhereClause where(Criteria criteria) {
this.whereDelegate.and(criteria);
return where;
}
@Override
public FluentWhereClause where(Object... criteria) {
this.whereDelegate.and(criteria);
return where;
}
@Override
public FluentGroupByClause groupBy(Column column, Column... columns) {
this.groupByDelegate.add(column, columns);
return groupBy;
}
@Override
public FluentGroupByClause groupBy(String column, String... columns) {
this.groupByDelegate.add(column, columns);
return groupBy;
}
@Override
public FluentHavingClause having(Column column, String condition) {
this.havingDelegate.and(column, condition);
return having;
}
@Override
public FluentHavingClause having(Object... columns) {
this.havingDelegate.and(columns);
return having;
}
@Override
public Query getQuery() {
return this;
}
public FluentOrderByClause orderBy() {
return orderBy;
}
@Override
public FluentOrderByClause orderBy(Column column, Order order) {
this.orderByDelegate.add(column, order);
return orderBy;
}
@Override
public FluentOrderByClause orderBy(Column col1, Order order1, Column col2, Order order2) {
this.orderByDelegate.add(col1, order1, col2, order2);
return orderBy;
}
@Override
public FluentOrderByClause orderBy(Column col1, Order order1, Column col2, Order order2, Column col3, Order order3) {
this.orderByDelegate.add(col1, order1, col2, order2, col3, order3);
return orderBy;
}
@Override
public FluentOrderByClause orderBy(String column, Order order) {
this.orderByDelegate.add(column, order);
return orderBy;
}
@Override
public FluentOrderByClause orderBy(String col1, Order order1, String col2, Order order2) {
this.orderByDelegate.add(col1, order1, col2, order2);
return orderBy;
}
@Override
public FluentOrderByClause orderBy(String col1, Order order1, String col2, Order order2, String col3, Order order3) {
this.orderByDelegate.add(col1, order1, col2, order2, col3, order3);
return orderBy;
}
@Override
public FluentOrderByClause orderBy(Column column, Column... columns) {
this.orderByDelegate.add(column, columns);
return orderBy;
}
@Override
public FluentOrderByClause orderBy(String column, String... columns) {
this.orderByDelegate.add(column, columns);
return orderBy;
}
@Override
public FluentLimitClause limit(int value) {
this.limitDelegate.setCount(value);
return limit;
}
@Override
public FluentLimitClause limit(int value, Integer offset) {
this.limitDelegate.setCount(value, offset);
return limit;
}
@Override
public Union unionAll(QueryProvider<Query> query) {
return new Union(this, query.getQuery());
}
public interface FluentSelectClause extends SelectChain<FluentSelectClause>, FromAware, QueryProvider<Query> {
/**
* Overridden to return {@link FluentSelectClauseAliasableExpression} aimed at giving {@link FluentSelectClause} allowing to chain with
* methods of {@link SelectChain}
*
* @param expression
* @param javaType
* @return an object that applies {@link AliasableExpression} methods and dispatches {@link FluentSelectClause} calls to surrounding instance
*/
@Override
FluentSelectClauseAliasableExpression add(String expression, Class<?> javaType);
}
/**
* A mixin of {@link AliasableExpression} and {@link FluentSelectClause} to allow chaining of {@link FluentSelectClause} methods after {@link #as(String)}
*/
public interface FluentSelectClauseAliasableExpression extends AliasableExpression<FluentSelectClause>, FluentSelectClause {
@Override
FluentSelectClause as(String alias);
}
public interface FluentFromClause extends JoinChain<FluentFromClause>, WhereAware, GroupByAware, OrderByAware, LimitAware<FluentLimitClause>, QueryProvider<Query> {
}
public interface FluentWhereClause extends CriteriaChain<FluentWhereClause>, GroupByAware, OrderByAware, LimitAware<FluentLimitClause>, UnionAware, QueryProvider<Query> {
}
public interface FluentGroupByClause extends GroupByChain<FluentGroupByClause>, HavingAware, OrderByAware, LimitAware<FluentLimitClause>, UnionAware, QueryProvider<Query> {
}
public interface FluentHavingClause extends CriteriaChain<FluentHavingClause>, OrderByAware, LimitAware<FluentLimitClause>, UnionAware, QueryProvider<Query> {
}
public interface FluentOrderByClause extends OrderByChain<FluentOrderByClause>, LimitAware<FluentLimitClause>, UnionAware, QueryProvider<Query> {
}
public interface FluentLimitClause extends LimitChain<FluentLimitClause>, UnionAware, QueryProvider<Query> {
}
}